import datetime
import os
import openpyxl
import re

OUTPUT_FORMATS = [
    r'\s*(.*\w+.*).*:\s*(\d+\.\d+).+ms',      # Function Call - RunNormalCall:  44.46125        ms
    r'\s*(.*\w+.*).*:(\d+)ms',                # Array Access - RunFastaRandom2: 30 ms
    r'\s*(.*\w+.*).*:\s*(\d+).+ms',           # 'Splay:  3224    ms'
    r'^(\w+): ms = (\d+.\d+)',                # 'box2d: ms = 270.03125'
    r'^(\S+): ms = (\d+.\d+)',                # 'crypto-sha1: ms = 270.03125'
    r'^(\S+): ms = (\d+)',                    # 'crypto-aes: ms = 40'
    r'^(\S+): ms =\s+(\d+.\d+)',              # 'crypto-aes:  ms = 40.1213'
    r'^(\S+): ms =\s+(\d+)',                  # 'crypto-aes: ms  = 40'
    r'^\s*scene_output:\s*(\w+.*):\s*(\d+)',  # builtin  scene_output: AggregateError constructor AggregateErrorOfConstructor time: 378
    r'\s*(.*\w+.*).*:\s*(\d+\.\d+).+us',      # Function Call - RunNormalCall:  44.46125        us
    r'\s*(.*\w+.*).*:(\d+)us',                # Array Access - RunFastaRandom2: 30 us
    r'\s*(.*\w+.*).*:\s*(\d+).+us',           # 'Splay:  3224    us'
    r'^(\w+): us = (\d+.\d+)',                # 'box2d: us = 270.03125'
    r'^(\S+): us = (\d+.\d+)',                # 'crypto-sha1: us = 270.03125'
    r'^(\S+): us = (\d+)',                    # 'crypto-aes: us = 40'
    r'^(\S+): us =\s+(\d+.\d+)',              # 'crypto-aes:  us = 40.1213'
    r'^(\S+): us =\s+(\d+)',                  # 'crypto-aes: us  = 40'
]

OUTPUT_PATTERNS = [re.compile(v) for v in OUTPUT_FORMATS]

def get_case_name_and_time_from_str(msg: str) -> [str, int]:
    for pattern in OUTPUT_PATTERNS:
        result = pattern.search(msg)
        if result:
            return result.group(1), result.group(2)
    return None, None

class ReportDetail():
    __case_name: str
    __execute_successful: bool
    __error_message: str
    __mobile_temperatura: float
    __mobile_electricity: int
    __mobile_cpu: bool
    __case_test_point: dict

    def __init__(
            self,
            case_name,
            execute_successful = True,
            error_message = '',
            mobile_temperatura = 0,
            mobile_electricity = 0
            ):
        self.__case_name = case_name
        self.__execute_successful = execute_successful
        self.__error_message = error_message
        self.__mobile_temperatura = mobile_temperatura
        self.__mobile_electricity = mobile_electricity
        self.__case_test_point = {}

    def get_case_name(self):
        return self.__case_name

    def get_execute_successful(self):
        return self.__execute_successful

    def set_execute_successful(self, is_succ: bool):
        self.__execute_successful = is_succ

    def get_error_message(self):
        return self.__error_message

    def set_error_message(self, error_message: str):
        self.__error_message = error_message

    def set_mobile_temperatura(self, mobile_temperatura: float):
        self.__mobile_temperatura = mobile_temperatura

    def set_mobile_electricity(self, mobile_electricity: int):
        self.__mobile_electricity = mobile_electricity

    def add_test_point(self, key: str, value: int):
        self.__case_test_point[key] = value

    def get_test_points(self):
        return self.__case_test_point

class ExecuteReport():
    __test_suite_name: str
    __test_mode: str
    __mobile_sn: str
    __test_begin_time: datetime
    __all_report_details: list

    def __init__(
            self,
            test_suite_name,
            test_mode,
            mobile_sn,
            test_begin_time,
            all_report_details):
        self.__test_suite_name = test_suite_name
        self.__test_mode = test_mode
        self.__mobile_sn = mobile_sn
        self.__test_begin_time = test_begin_time
        self.__all_report_details = all_report_details

    def report(self, client_logger):
        count = 0
        result = {}
        for report_details in self.__all_report_details:
            count = count + 1
            for detail in report_details:
                if not detail.get_execute_successful():
                    continue;
                for key, value in detail.get_test_points().items():
                    if key not in result:
                       result[key] = []
                    result[key].append(value)
            for key, values in result.items():
                if len(values) < count:
                    result[key].append(-1)
        # calculate the average.
        for key, values in result.items():
            total = 0.0
            for value in values:
                if value == -1:
                    total = 0
                    break
                total = total + float(value)
            result[key].append(total/count)
        # print result
        for key, values in result.items():
            msg = f'{key:<50}'
            for value in values:
                msg += f'{value:<20}'
            print(msg)
            client_logger.info(msg)

    def report_summary(self, external_information: str, client_logger):
        fail_cases = set([])
        for report_details in self.__all_report_details:
            for report_detail in report_details:
                if not report_detail.get_execute_successful():
                    fail_cases.add(report_detail.get_case_name())
        message = 'test suite: ' + self.__test_suite_name + '\ntotal: ' + str(len(self.__all_report_details[0])) + '\nfail: ' + str(fail_cases) + "\n" + external_information
        print(message)
        client_logger.info(message)

    def report_detail(self):
        for detail in self.__report_details:
            if not detail.get_execute_successful():
                print(f'error msg: {detail.get_error_message()}')
                continue
            for key, value in detail.get_test_points().items():
                print(f'key: {key}, value: {value}')

    def is_part_of_merged_cell(self, cell, sheet):
        for merged_cell in sheet.merged_cells:
            if cell.coordinate in merged_cell:
                return True
        return False

    def get_merged_cell(self, cell, sheet):
        for merged_cell in sheet.merged_cells:
            if cell.coordinate in merged_cell:
                return merged_cell
        return None

    def insert_data_column(self, data_column, sheet):
        cell = sheet[f'{openpyxl.utils.get_column_letter(data_column)}1']
        if self.is_part_of_merged_cell(cell, sheet):
            merged_cell = self.get_merged_cell(cell, sheet)
            merged_cell_min_col = merged_cell.min_col
            merged_cell_max_col = merged_cell.max_col
            merged_cell_begin = f'{openpyxl.utils.get_column_letter(merged_cell_min_col)}1'
            merged_cell_end = f'{openpyxl.utils.get_column_letter(merged_cell_max_col)}1'
            sheet.unmerge_cells(f'{merged_cell_begin}:{merged_cell_end}')
            sheet.insert_cols(data_column)
            merged_cell_min_col += 1
            merged_cell_max_col += 1
            merged_cell_begin = f'{openpyxl.utils.get_column_letter(merged_cell_min_col)}1'
            merged_cell_end = f'{openpyxl.utils.get_column_letter(merged_cell_max_col)}1'
            sheet.merge_cells(f'{merged_cell_begin}:{merged_cell_end}')
        else:
            sheet.insert_cols(data_column)

    def create_data_column(self, sheet, tag: str):
        data_column = 0
        title_row = sheet['1']
        for title_cell in title_row:
            # skip test name column and test point name column.
            if title_cell.column == 1 or title_cell.column == 2:
                continue
            if self.is_part_of_merged_cell(title_cell, sheet):
                if title_cell.value == tag:
                    merged_cell = self.get_merged_cell(title_cell, sheet)
                    # min_row and max_row must be 1.
                    data_column = merged_cell.max_col + 1
                    self.insert_data_column(data_column, sheet)
                    begin = f'{openpyxl.utils.get_column_letter(merged_cell.min_col)}1'
                    end = f'{openpyxl.utils.get_column_letter(merged_cell.max_col)}1'
                    sheet.unmerge_cells(f'{begin}:{end}')
                    end = f'{openpyxl.utils.get_column_letter(data_column)}1'
                    sheet.merge_cells(f'{begin}:{end}')
                    break
            else:
                if title_cell.value == None:
                    data_column = title_cell.column
                    sheet.insert_cols(data_column)
                    sheet[data_column][1] = tag
                    break
                elif title_cell.value == tag:
                    data_column = title_cell.column + 1
                    self.insert_data_column(data_column, sheet)
                    begin = f'{openpyxl.utils.get_column_letter(title_cell.column)}1'
                    end = f'{openpyxl.utils.get_column_letter(data_column)}1'
                    sheet.merge_cells(f'{begin}:{end}')
                    break
        if data_column == 0:
            data_column = sheet.max_column + 1
            sheet.column_dimensions[openpyxl.utils.get_column_letter(data_column)].width = 20
            sheet[f'{openpyxl.utils.get_column_letter(data_column)}1'] = tag
        return openpyxl.utils.get_column_letter(data_column)

    def find_test_name_row_range(self, sheet, test_name_column_letter: str, test_name: str):
        test_name_column = sheet[test_name_column_letter]
        for cell in test_name_column:
            if cell.value != test_name:
                continue
            if self.is_part_of_merged_cell(cell, sheet):
                merged_cell = self.get_merged_cell(cell, sheet)
                return [merged_cell.min_row, merged_cell.max_row]
            else:
                return [cell.row, cell.row]
        return [None, None]

    def find_test_point_name_row(self,
                                 sheet,
                                 test_point_name_column_letter,
                                 test_name_min_row,
                                 test_name_max_row,
                                 test_point_name):
        test_point_name_column = sheet[test_point_name_column_letter]
        for cell in test_point_name_column:
            if cell.row < test_name_min_row:
                continue
            if cell.row > test_name_max_row:
                continue
            if cell.value == test_point_name or cell.value == None:
                return cell.row
        return None

    def insert_test_name_row(self, sheet, test_name_row):
        sheet.insert_row(test_name_row)

    def insert_test_point_name_row(self, sheet, test_name_column_letter, test_point_name_row):
        test_name_cell = sheet[f'{test_name_column_letter}{test_point_name_row}']
        if self.is_part_of_merged_cell(test_name_cell, sheet):
            test_name_merged_cell = self.get_merged_cell(test_name_cell, sheet)
            sheet.unmerge_cells(f'{test_name_merged_cell.min_col}{test_name_merged_cell.min_row}:{test_name_merged_cell.max_col}{test_name_merged_cell.max_row}')
            sheet.insert_row(test_point_name_row)
            sheet.merge_cells(f'{test_name_merged_cell.min_col}{test_name_merged_cell.min_row}:{test_name_merged_cell.max_col}{test_name_merged_cell.max_row + 1}')
        else:
            sheet.insert_row(test_point_name_row)

    def insert_test_data(self,
                         sheet,
                         test_name_column_letter,
                         test_point_name_column_letter,
                         data_column_letter):
        for detail in self.__report_details:
            test_name = detail.get_case_name()
            test_name_min_row, test_name_max_row = self.find_test_name_row_range(sheet, test_name_column_letter, test_name)
            # Unable to find the target test_name, indicating that this is a newly added test. Add a line at the end of the sheet to record the test results.
            if test_name_min_row == None and test_name_max_row == None:
                test_name_min_row = sheet.max_row + 1
                test_name_max_row = test_name_min_row
                self.insert_test_name_row(sheet, test_name_min_row)
                sheet[f'{test_name_column_letter}{test_name_min_row}'] = test_name
            for test_point_name, test_point_value in detail.get_test_points().items():
                test_point_name_row = self.find_test_point_name_row(sheet, test_point_name_column_letter, test_name_min_row, test_name_max_row, test_point_name)
                if test_point_name_row == None:
                    test_name_max_row = test_name_max_row + 1
                    test_point_name_row = test_name_max_row
                    self.insert_test_point_name_row(sheet, test_name_column_letter, test_point_name_row)
                    test_name_cell = sheet[f'{test_name_column_letter}{test_name_min_row}']
                    if self.is_part_of_merged_cell(test_name_cell, sheet):
                        test_name_merged_cell = self.get_merged_cell(test_name_cell, sheet)
                        sheet.unmerge_cells(f'{test_name_column_letter}{test_name_min_row}:{test_name_column_letter}{test_name_merged_cell.max_row}')
                    sheet.merge_cells(f'{test_name_column_letter}{test_name_min_row}:{test_name_column_letter}{test_point_name_row}')
                    sheet[f'{test_point_name_column_letter}{test_point_name_row}'] = test_point_name
                sheet[f'{data_column_letter}{test_point_name_row}'] = test_point_value
            if not detail.get_execute_successful():
                red_background = openpyxl.styles.colors.Color(rgb='FFFF0000')
                data_cell_range = sheet[f'{data_column_letter}{test_name_min_row}:{data_column_letter}{test_name_max_row}']
                for data_cell in data_cell_range:
                    data_cell.fill = red_background

    def column_width_adaptation(self, sheet):
        for column in range(1, sheet.max_column):
            column_letter = openpyxl.utils.get_column_letter(column)
            max_length = 0
            for cell in sheet[column_letter]:
                cell.alignment = openpyxl.styles.Alignment(horizontal='center')
                cur_length = len(str(cell.value))
                if cur_length > max_length:
                    max_length = cur_length
                sheet.column_dimensions[column_letter].width = max_length + 2

    def generate_excel_report(self, excel_file_full_name: str, tag: str):
        detail_sheet_name = 'detail'
        # get excel file
        if not os.path.exists(excel_file_full_name):
            workbook = openpyxl.Workbook(excel_file_full_name)
            workbook.save(excel_file_full_name)
        workbook = openpyxl.load_workbook(excel_file_full_name)
        # get detail sheet
        if not (detail_sheet_name in workbook.sheetnames):
            workbook.create_sheet(detail_sheet_name)
        detail_sheet = workbook.get_sheet_by_name(detail_sheet_name)
        test_name_column_letter = 'A'
        detail_sheet[f'{test_name_column_letter}1'].value = 'test_name'
        test_point_name_column_letter = 'B'
        detail_sheet[f'{test_point_name_column_letter}1'].value = 'test_point_name'
        # find data insertion location:
        # 1. when the same tag exists in the sheet, insert it into the column after the tag.
        # 2. when there are no identical tags in the sheet, insert them into the first empty column.
        data_column_letter = self.create_data_column(detail_sheet, tag)
        self.insert_test_data(sheet, test_name_column_letter, test_point_name_column_letter, data_column_letter)
        # Column width adaptation
        self.column_width_adaptation(detail_sheet)
        workbook.save(excel_file_full_name)
